﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using Tamir.IPLib;
using Tamir.IPLib.Packets;
using Timer=System.Timers.Timer;

namespace TCPSnifferApp
{
	public class PcapManager
	{
		private const int cMaxNotificationListSize = 50;
		private List<PcapDevice> mDevices;
		private Queue<Packet> mPacketQueue;
		private Timer mEventTimer;
		private BackgroundWorker mBackgroundWorker;
		private TimeSpan mCaptureInterval;

		private static volatile object syncRoot = new object();
        
		private event EventHandler<PacketReceivedEventArgs> ePacketsReceived;

		public TimeSpan CaptureInterval
		{
			get { return mCaptureInterval; }
			set { mCaptureInterval = value; }
		}

		public TimeSpan NotificationInterval
		{
			get { return TimeSpan.FromMilliseconds(mEventTimer.Interval); }
			set { mEventTimer.Interval = (value != TimeSpan.Zero ? value.TotalMilliseconds : 100); }
		}

		private void InvokePacketsReceived(PacketReceivedEventArgs e)
		{
			EventHandler<PacketReceivedEventArgs> received = ePacketsReceived;
			if (received != null) received(this, e);
		}

		public event EventHandler<PacketReceivedEventArgs> PacketsReceived
		{
			add { ePacketsReceived += value; }
			remove { ePacketsReceived -= value; }
		}

		public PcapManager(TimeSpan notificationInterval, TimeSpan captureInterval)
		{
			mDevices = SharpPcap.GetAllDevices().Cast<PcapDevice>().ToList();
			mPacketQueue = new Queue<Packet>();
			mEventTimer = new Timer(notificationInterval.TotalMilliseconds);

			mBackgroundWorker = new BackgroundWorker();
			mBackgroundWorker.WorkerReportsProgress = true;
			mBackgroundWorker.WorkerSupportsCancellation = true;

			mBackgroundWorker.DoWork += mBackgroundWorker_DoWork;
			mBackgroundWorker.ProgressChanged += mBackgroundWorker_ProgressChanged;

			mEventTimer.Elapsed += NotificationIntervalElapsed;
			CaptureInterval = captureInterval;
		}

		void mBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			Packet packet = (Packet)e.UserState;
			lock (syncRoot)
			{
				mPacketQueue.Enqueue(packet);
			}
		}

		void mBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
		{

			foreach (PcapDevice device in mDevices)
			{
				device.PcapOnPacketArrival += device_PcapOnPacketArrival;

				if (!device.PcapOpened)
				{
					device.PcapOpen(true, (int)CaptureInterval.TotalMilliseconds);
				}

				if (!device.PcapStarted)
				{
					device.PcapStartCapture();
				}
			}
			while (!mBackgroundWorker.CancellationPending)
			{
				if (CaptureInterval != TimeSpan.Zero)
				{
					Thread.Sleep(CaptureInterval);
				}
			}		
		}

		void device_PcapOnPacketArrival(object sender, Packet packet)
		{
			mBackgroundWorker.ReportProgress(0, packet);					
		}

		void NotificationIntervalElapsed(object sender, ElapsedEventArgs e)
		{
			int packetsCount = mPacketQueue.Count % cMaxNotificationListSize;

			List<Packet> packets = new List<Packet>();

			for (int packetStep = 0; packetStep < packetsCount; packetStep++)
			{
				Packet packet = mPacketQueue.Dequeue();
				packets.Add(packet);
			}

			if (packets.Count > 0)
			{				
				InvokePacketsReceived(new PacketReceivedEventArgs(packets));
			}
		}

		public void Start()
		{
			lock (syncRoot)
			{
				mPacketQueue.Clear();
			}
			mEventTimer.Start();

			mBackgroundWorker.RunWorkerAsync();
		}

		public void Stop()
		{
			mBackgroundWorker.CancelAsync();

			foreach (PcapDevice device in mDevices)
			{
				if (device.PcapStarted)
				{
					device.PcapStopCapture();
				}

				if (device.PcapOpened)
				{
					device.PcapClose();
				}
			}
			mEventTimer.Stop();
		}
	}
}
